﻿using UnityEngine;
using System.Collections;
using Game.Global.Buffer;
using System.Collections.Generic;

namespace Game.Global.Helper
{
	public static class TerrainHelper 
	{
        //---------------------------------------------------------------------------
        //				Public area
        //---------------------------------------------------------------------------
        #region Public area
        /// <summary>
        /// 在Terrain上方生成对应数量的item物体
        /// </summary>
        /// <param name="itemPrefab">物体prefab</param>
        /// <param name="terrainObj">在哪个物体表面生成</param>
        /// <param name="count">数量</param>
        /// <param name="baseDeltaY">基础偏移值：（因地形高度近似值，故增加一个偏移值确保不会在地形之下）</param>
        /// <param name="randDeltaYRange">增加随机的高度（0~deltaY）</param>
        /// <returns></returns>
        public static GameObject[] GenerateItemsAboveTerrain(GameObject itemPrefab, Terrain terrainObj, int count, float baseDeltaY = 5, float randDeltaYRange = 5)
        {
            return GenerateItemsOnTerrain(itemPrefab, null, terrainObj, count,false, false,baseDeltaY, randDeltaYRange);
        }

        /// <summary>
        /// 在Terrain上方生成对应数量的item物体（从buffer中生成物体）
        /// </summary>
        /// <param name="itemBuffer"></param>
        /// <param name="terrainObj">在哪个物体表面生成</param>
        /// <param name="count">数量</param>
        /// <param name="baseDeltaY">基础偏移值：（因地形高度近似值，故增加一个偏移值确保不会在地形之下）</param>
        /// <param name="randDeltaYRange">增加随机的高度（0~deltaY）</param>
        /// <returns></returns>
        public static GameObject[] GenerateItemsAboveTerrain(GameObjBuffer itemBuffer, Terrain terrainObj, int count, float baseDeltaY = 5, float randDeltaYRange = 5)
        {
            return GenerateItemsOnTerrain(null,itemBuffer, terrainObj, count, false, false, baseDeltaY, randDeltaYRange);
        }

        /// <summary>
        /// 在Terrain表面生成对应数量的物体，且保持与Prefab的链接
        /// </summary>
        /// <param name="itemPrefab"></param>
        /// <param name="terrainObj"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static GameObject[] GenerateDetailsOnTerrain(GameObject itemPrefab,Terrain terrainObj, int count,Vector3 srcPoint,float rangeX,float rangeZ)
        {
            return GenerateItemsOnTerrain(itemPrefab, null, terrainObj, count, true,true, 0, 0,srcPoint,rangeX,rangeZ);
        }

        /// <summary>
        /// <para>只根据地形的长、宽来生成一个随机位置</para>
        /// <para>Generates a random position with terrain's width &amp; length</para>
        /// </summary>
        /// <returns>A random position.</returns>
        /// <param name="srcPosition">偏移位置（随机到的位置会以此偏移，如果不传，则为地形左下角，即在地形内随机）Source position(left down corner).</param>
        /// <param name="rangeX">Range to maxX</param>
        /// <param name="rangeZ">maxZ</param>
        public static Vector3 GenerateRandomPosition(Terrain terrainObj, Vector3? srcPosition = null , float rangeX=0, float rangeZ=0)
        {
            //没有传入随机的范围的话，则取地形大小
            if (Mathf.Abs(rangeX) <= 0.0001f && Mathf.Abs(rangeZ) <= 0.0001f)
            {
                var terrainCollider = terrainObj.GetComponent<TerrainCollider>();

                //获得宽度  
                var tarObjSize = terrainCollider.bounds.size;
                var tarObjScale = terrainCollider.transform.localScale;

                //计算实际的大小：宽度、长度（x、z）
                rangeX= tarObjSize.x * tarObjScale.x;
                rangeZ = tarObjSize.z * tarObjScale.z;
            }

            //TODO:最好限定范围最大为地形大小

            //没有传入起始位置，则取用Terrain 的位置
            if(srcPosition==null)
            {
                srcPosition = terrainObj.transform.position;
            }

            Vector3 result = srcPosition.Value;

            result.x += Random.Range(0, rangeX);
            result.z += Random.Range(0, rangeZ);

            return result;
        }

        /// <summary>
        /// 获得某个坐标在地形中的近似高度（使用高度图计算，常用于在地图上方散布Item）
        /// <para>注意：对于超出地形范围的，仍然会返回不确定的高度</para>
        /// </summary>
        /// <param name="terrain">地形物体</param>
        /// <param name="worldPos">世界坐标</param>
        /// <returns>在地形上的近似高度（有偏差）</returns>
        public static float GetProbableHeight(this Terrain terrain, Vector3 worldPos)
        {
            var terrainData = terrain.terrainData;

            //获得该坐标在地形中的偏移位置：
            Vector3 localPos = worldPos - terrain.transform.position;   
            Vector3 terrainSize = terrainData.size;
            
            //计算这个坐标在二维高度图中的x、y坐标
            Vector2 terrainMapPos = new Vector2(localPos.x / terrainSize.x * terrainData.heightmapWidth, localPos.z / terrainSize.z * terrainData.heightmapHeight);

            //获得高度图中，这个坐标的对应高度：（会有偏差）
            return terrainData.GetHeight((int)terrainMapPos.x, (int)terrainMapPos.y);
        }

        /// <summary>
        /// 获得一批坐标在地形中的精确高度（使用物理射线，常用于生成绝对在表面上的附着物：如植物）
        /// <para>注意：对于无法命中到Terrain的数组位置返回(0,0,0)，所以对于（0,0）的情况需要自行避免</para>
        /// </summary>
        /// <param name="terrain">地形物体</param>
        /// <param name="worldPos">世界坐标，只会用到x、z；y将被重新计算</param>
        /// <returns></returns>
        public static Vector3[] GetPrecisePos(this Terrain terrain, Vector3[] worldPos)
        {
            var terrainData = terrain.terrainData;
            float terrainMaxHeight = terrainData.size.y;

            RaycastHit hitInfo;
            for (int i = 0;i<worldPos.Length;i++)
            {
                worldPos[i].y = terrainMaxHeight;
                Debug.Log("Pre pos:" + worldPos[i]);
                if (Physics.Raycast(worldPos[i], Vector3.down, out hitInfo) 
                    && hitInfo.transform == terrain.transform)                      //命中，且命中Terrain：
                {
                    worldPos[i] = hitInfo.point;
                    Debug.Log("After pos:" + worldPos[i]);
                }
                else
                {
                    Debug.LogError("Cannot hit terrain at " + worldPos[i]);
                    worldPos[i] = Vector3.zero;
                }
            }

            return worldPos;
        }

        /// <summary>
        /// 获得一个在地形中的精确高度（使用物理射线，常用于生成绝对在表面上的附着物：如植物）
        /// <para>注意：对于无法命中到Terrain的位置返回float的最小数</para>
        /// </summary>
        /// <param name="terrainObj">地形物体</param>
        /// <param name="worldPos">世界坐标，只会用到x、z；y将被重新计算</param>
        /// <returns></returns>
        public static float GetPreciseHeight(Terrain terrainObj, Vector3 worldPos)
        {
            var terrainData = terrainObj.terrainData;
            RaycastHit hitInfo;
            float terrainMaxHeight = terrainData.size.y;

            worldPos.y = terrainMaxHeight;
            //Debug.Log("Pre pos:" + worldPos);

            if (Physics.Raycast(worldPos, Vector3.down, out hitInfo)
                && hitInfo.transform == terrainObj.transform)                      //命中，且命中Terrain：
            {
                worldPos = hitInfo.point;
                //Debug.Log("After pos:" + worldPos);
            }
            else
            {
                Debug.LogWarning("Cannot hit terrain at " + worldPos);
                worldPos.y = float.MinValue;
            }

            return worldPos.y;
        }
        #endregion

        //---------------------------------------------------------------------------
        //				Private area
        //---------------------------------------------------------------------------
        #region Private area
        /// <summary>
        /// 在目标物体表面的某个范围内生成不多于对应数量的item物体。
        /// <para>PS：当越界时，有可能返回少于对应数量的物体</para>
        /// </summary>
        /// <returns>The items.</returns>
        /// <param name="itemPrefab">Prefab of item.但是如果有buffer，则不取prefab内容。</param>
        /// <param name="count">Count of item to generate.</param>
        /// <param name="onPreciseSurface">是否在精确的表面上（将决定计算地形高度是否用精确的射线）</param>
        /// <param name="keepLinkToPrefab">是否保持到prefab的链接（只有在itemPrefab作为参数的情况下有效）</param>
        /// <param name="objBuffer">if you have a buffer, the item will be generated from this buffer.</param>
        /// <param name="terrainObj">Terrain</param>
        /// <param name="baseDeltaY">基础偏移值：（因地形高度近似值，故增加一个偏移值确保不会在地形之下）</param>
        /// <param name="randDeltaY">增加随机的高度（0~deltaY）</param>
        /// <param name="srcPoint">随机生成的起始点（左下角），null则取地形的position</param>
        /// <param name="surfX">随机生成的X范围（宽度），不填则取整个地形</param>
        /// <param name="surfZ">随机生成的Z范围（长度），不填则取整个地形</param>
        private static GameObject[] GenerateItemsOnTerrain(GameObject itemPrefab, GameObjBuffer objBuffer, Terrain terrainObj, int count, bool onPreciseSurface, bool keepLinkToPrefab, float baseDeltaY, float randDeltaY, Vector3? srcPoint = null, float surfX = 0, float surfZ = 0)
        {
#if UNITY_EDITOR
            if (itemPrefab == null)
            {
                Debug.LogError("No item prefab given");
                return null;
            }
#endif

            List<GameObject>resultObjs = new List<GameObject>();

            //表面长、宽 (x/z)


            //确定目标物体的大小
            //--对于地形：（考虑size，不考虑Collider的偏移）
            if (terrainObj == null)
            {
                Debug.LogError("Target is not a terrain");
                return null;
            }

            //随机的起始位置：
            Vector3 randPos = Vector3.zero;
            GameObject objInstance = null;

            //开始范围内的位置生成：
            for (int i = 0; i < count; i++)
            {
                //生成一个限定在地形范围内的坐标：
                randPos = GenerateRandomPosition(terrainObj,srcPoint, surfX, surfZ);

                //是否要精确高度
                if (onPreciseSurface)
                {
                    //用物理射线获得精确的高度
                    randPos.y = GetPreciseHeight(terrainObj, randPos);
                    if(randPos.y<= float.MinValue)
                    {
                        Debug.LogWarning("Cannot get the precise height of terrain at " + randPos + ", so will skip this position");
                        continue;
                    }
                }
                else
                {
                    //获得这个坐标的对应高度：(由于是近似高度，所以需要加上一点偏移值+随机的高度值）
                    //PS:需要注意这里超出地形的情况下仍然会返回错误的值：
                    randPos.y = GetProbableHeight(terrainObj, randPos) + baseDeltaY + Random.Range(0, randDeltaY);
                }

                //保持prefab时，(且编辑器模式)调用Editor的方法，绝对不会使用Buffer：
                if (keepLinkToPrefab)
                {
#if UNITY_EDITOR
                    objInstance = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(itemPrefab);
#else 
                    if (objBuffer != null)
                    {
                        objInstance = objBuffer.PopObjectBuffered();
                    }
                    else
                    {
                        objInstance = (GameObject)GameObject.Instantiate(itemPrefab);
                    }
#endif
                }
                else
                {
                    objInstance = (GameObject)GameObject.Instantiate(itemPrefab);
                }

                

                objInstance.transform.position = randPos;
                resultObjs.Add( objInstance);
            }


            return resultObjs.ToArray();
        }

        #endregion
    }
}